#include <stdio.h>
#include "comm.h"
#include "draw_staff.h"

/////////////////// CONSTANTS ///////////////////

#define MACRO_RETURN_COUNT	-3

#define ADDR_INC			"4:w"
#define ADDR_DEC			"-4:w"

#ifdef	FAST_STACK

#define STACK_PUSH_COUNT	-6
#define STACK_POP_COUNT		-6

#else

#define STACK_PUSH_COUNT	-4
#define STACK_POP_COUNT		-4

#endif

#define PUSH_OTHER_COUNT	-3
#define POP_OTHER_COUNT		-3

static FILE *out;

//////////////////////////////// type converter //////////////////////////////////

const char *inum2str(char buf[], int num, enum itype t, int positive)
{
	switch (t)
	{
	case w:
		sprintf(buf, "0X%04X:w", positive ? num : -num);
		break;
	case uw:
		sprintf(buf, "0X%04X:uw", positive ? num : -num);
		break;
	case d:
		sprintf(buf, "0X%08X:d", positive ? num : -num);
		break;
	case ud:
		sprintf(buf, "0X%08X:ud", positive ? num : -num);
		break;
	case na:
	default:
		sprintf(buf, "0X%04X", positive ? num : -num);
		break;
	}
	return buf;
}

const char *float2str(char buf[], float num, int positive)
{
	sprintf(buf, "%f:f", positive ? num : -num);
	return buf;
}

const char *rnum2rname(char buf[], int num, enum itype t, int positive)
{
	switch (t)
	{
	case w:
		sprintf(buf, positive ? "r%d.%02d:w" : "-r%d.%02d:w", num / 8 + BASE_REG_OFFSET,
				(num % 8) * 4);
		break;
	case uw:
		sprintf(buf, positive ? "r%d.%02d:uw" : "-r%d.%02d:uw", num / 8 + BASE_REG_OFFSET,
				(num % 8) * 4);
		break;
	case d:
		sprintf(buf, positive ? "r%d.%02d:d" : "-r%d.%02d:d", num / 8 + BASE_REG_OFFSET,
				(num % 8) * 4);
		break;
	case ud:
		sprintf(buf, positive ? "r%d.%02d:ud" : "-r%d.%02d:ud", num / 8 + BASE_REG_OFFSET,
				(num % 8) * 4);
		break;
	case f:
		sprintf(buf, positive ? "r%d.%02d:f" : "-r%d.%02d:f", num / 8 + BASE_REG_OFFSET,
				(num % 8) * 4);
		break;
	case na:
	default:
		sprintf(buf, positive ? "r%d.%02d" : "-r%d.%02d", num / 8 + BASE_REG_OFFSET, (num % 8) * 4);
		break;
	}
	return buf;
}

//////////////////////////////// head & tail ///////////////////////////////////

int command_init(const char *filename, const char *l)
{
	char buf1[20], buf2[20], buf3[20];

	if ((out = fopen(filename, "wt")) == NULL)
	{
		fprintf(stderr, "Can't open output file: %s.\n", filename);
		return -1;
	}

	add_comment("all init");

#ifdef	FAST_STACK

	command_mov(1, REG_ESP, FAST_STACK_BOTTOM);

#else

	command_mov(1, REG_ESP, ZERO);

#endif

	graph_startup(inum2str(buf1, Y_RED, uw), inum2str(buf2, U_RED, uw), inum2str(buf3, V_RED, uw));

	macro_call(l);
	macro_EOT();

	return 0;
}

void command_terminate()
{
	add_comment("all terminate");

	fclose(out);
}

/////////////////////////////// miscs //////////////////////////////////

void add_label(const char *l)
{
	fprintf(out, "\n%s:\n", l);
}

void add_comment(const char *c)
{
	fprintf(out, "\n// %s\n", c);
}

void condition(const char *reg)
{
	fprintf(out, "(%s)\t", reg);
}

/////////////////////////////// macros ////////////////////////////////

void macro_call(const char *l)
{
	add_comment("macro_call");

	command_mov(1, REG_LINKAGE_0, REG_IP);
	command_jmpi(1, l);
}

void macro_return()
{
	char buf[20];

	add_comment("macro_return");

	command_mov(1, REG_LINKAGE_1, REG_IP);
	command_add(1, REG_LINKAGE_0, REG_LINKAGE_0, REG_LINKAGE_1N);
	// shift right by 3, which means "ip distance 16" divided by "jmpi distance 2"
	command_asr(1, REG_LINKAGE_0, REG_LINKAGE_0, inum2str(buf, 3, ud));
	command_add(1, REG_LINKAGE_0, REG_LINKAGE_0, inum2str(buf, MACRO_RETURN_COUNT * INST_LEN, d));
	command_jmpi(1, REG_LINKAGE_0);
}

void macro_EOT()
{
	add_comment("macro EOT");

	fprintf(out, "send(16)\t0 acc0<1>UW null thread_spawner(0, 0, 1) mlen 1 rlen 0 {EOT};\n\n");
}

void macro_media_block_write(unsigned short surface)
{
	fprintf(out, "send(16)\t0 null null data_port(5, 10, 0, %u, 0, 1) mlen 2 rlen 0;\n", surface);
}

void stack_push(const char *rname)
{
	add_comment("stack push");

#ifdef	FAST_STACK

	command_mov(1, REG_WRITE, rname);
	command_mov(1, REG_ADDR_CACHE, REG_ADDR);	// save REG_ADDR
	command_mov(1, REG_ADDR, REG_ESP);	// set position
	command_mov(1, REG_INDEX(d), REG_WRITE);	// WRITE INTO FAST_STACK
	command_mov(1, REG_ADDR, REG_ADDR_CACHE);	// recover REG_ADDR
	command_add(1, REG_ESP, REG_ESP, ADDR_DEC);	// UPDATE REG_ESP

#else

	command_mov(1, REG_M_RW_POS, REG_ESP);	// set position
	command_mov(1, REG_M_WRITE, rname);	// prepare new data
	fprintf(out, "send(16)\t0 null null data_port(5, 8, 0, %d, 0, 1) mlen 2 rlen 0;\n", STACK_SURFACE);	// write whole data
	command_add(1, REG_ESP, REG_ESP, ONE);	// update esp

#endif

}

static void stack_pop(const char *rname)
{
	add_comment("stack pop");

#ifdef	FAST_STACK

	command_add(1, REG_ESP, REG_ESP, ADDR_INC);	// UPDATE REG_ESP
	command_mov(1, REG_ADDR_CACHE, REG_ADDR);	// save REG_ADDR
	command_mov(1, REG_ADDR, REG_ESP);	// set position
	command_mov(1, REG_READ, REG_INDEX(d));	// READ FROM FAST_STACK
	command_mov(1, REG_ADDR, REG_ADDR_CACHE);	// recover REG_ADDR
	command_mov(1, rname, REG_READ);

#else

	command_add(1, REG_ESP, REG_ESP, NEG_ONE);
	command_mov(1, REG_M_RW_POS, REG_ESP);
	fprintf(out, "send(16)\t0 r0 null data_port(5, 0, 0, %d, 0, 1) mlen 1 rlen 1;\n",
			STACK_SURFACE);
	command_mov(1, rname, REG_READ);	// prepare new data

#endif

}

void macro_push(int regpos, unsigned int size)
{
	char buf[20];

	add_comment("macro_push");

	command_mov(1, REG_ADDR, inum2str(buf, BASE_ADDR_OFFSET + regpos * ADDR_PER_DWORD, uw));

	// jump to here
	stack_push(REG_INDEX(d));
	command_add(1, REG_ADDR, REG_ADDR, ADDR_INC);

	command_cmp(1, LESS, REG_COND, REG_ADDR,
				inum2str(buf, BASE_ADDR_OFFSET + (regpos + size) * ADDR_PER_DWORD, uw));
	condition(REG_COND);
	command_jmpi(1, inum2str(buf, (STACK_PUSH_COUNT + PUSH_OTHER_COUNT) * INST_LEN, na));
}

void macro_pop(int regpos, unsigned int size)
{
	char buf[20];

	add_comment("macro_pop");

	command_mov(1, REG_ADDR,
				inum2str(buf, BASE_ADDR_OFFSET + (regpos + size) * ADDR_PER_DWORD, uw));

	// jump to here
	command_add(1, REG_ADDR, REG_ADDR, ADDR_DEC);
	stack_pop(REG_INDEX(d));

	command_cmp(1, GREATER, REG_COND, REG_ADDR,
				inum2str(buf, BASE_ADDR_OFFSET + regpos * ADDR_PER_DWORD, uw));
	condition(REG_COND);
	command_jmpi(1, inum2str(buf, (STACK_POP_COUNT + POP_OTHER_COUNT) * INST_LEN, na));

	output_lines("");
}

void push_linkage()
{
	add_comment("push linkage");

	stack_push(REG_LINKAGE_0);
}

void pop_linkage()
{
	add_comment("pop linkage");

	stack_pop(REG_LINKAGE_0);
}

////////////////////////////// unit operations ///////////////////////////////////

void command_mov(unsigned short oplen, const char *des, const char *src)
{
	fprintf(out, "mov(%u)\t%s\t%s;\n", oplen, des, src);
}

void command_add(unsigned short oplen, const char *des, const char *src0, const char *src1)
{
	fprintf(out, "add(%u)\t%s\t%s\t%s;\n", oplen, des, src0, src1);
}

void command_mul(unsigned short oplen, const char *des, const char *src0, const char *src1)
{
	fprintf(out, "mul(%u)\t%s\t%s\t%s;\n", oplen, des, src0, src1);
}

void command_divint(unsigned short oplen, const char *des, const char *src0, const char *src1)
{
	command_mov(1, REG_SRC0, src0);
	command_mov(1, REG_SRC1, src1);
	fprintf(out, "math(1)\t%s\t%s\t%s\tINTDIV;\n", REG_DES, REG_SRC0, REG_SRC1);
	command_mov(1, des, REG_DES);
}

void command_modint(unsigned short oplen, const char *des, const char *src0, const char *src1)
{
	command_mov(1, REG_SRC0, src0);
	command_mov(1, REG_SRC1, src1);
	fprintf(out, "math(1)\t%s\t%s\t%s\tINTMOD;\n", REG_DES, REG_SRC0, REG_SRC1);
	command_mov(1, des, REG_DES);
}

void command_divmodint(unsigned short oplen, const char *des0, const char *des1, const char *src0,
					   const char *src1)
{
	command_mov(1, REG_SRC0, src0);
	command_mov(1, REG_SRC1, src1);
	fprintf(out, "math(1)\t%s\t%s\t%s\tINTDIVMOD;\n", REG_DES, REG_SRC0, REG_SRC1);
	command_mov(1, des0, REG_DES);
	command_mov(1, des1, REG_DES1);
}

void command_powfloat(unsigned short oplen, const char *des, const char *src0, const char *src1)
{
	command_mov(1, REG_SRC0_F, src0);
	command_mov(1, REG_SRC1_F, src1);
	fprintf(out, "math(1)\t%s\t%s\t%s\tPOW;\n", REG_DES, REG_SRC0, REG_SRC1);
	command_mov(1, des, REG_DES_F);
}

void command_divfloat(unsigned short oplen, const char *des, const char *src0, const char *src1)
{
	command_mov(1, REG_SRC1_F, src1);
	fprintf(out, "math(1)\t%s\t%s\tnull\tINV;\n", REG_SRC1_F, REG_SRC1_F);
	command_mul(1, des, REG_SRC1_F, src0);
}

void command_mathfloat(unsigned short oplen, const char *des, const char *src, enum mtype t)
{
	command_mov(oplen, des, src);
	switch (t)
	{
	case INV:
		fprintf(out, "math(%u)\t%s\t%s\tnull\tINV;\n", oplen, des, des);
		break;
	case LOG:
		fprintf(out, "math(%u)\t%s\t%s\tnull\tLOG;\n", oplen, des, des);
		break;
	case EXP:
		fprintf(out, "math(%u)\t%s\t%s\tnull\tEXP;\n", oplen, des, des);
		break;
	case SQRT:
		fprintf(out, "math(%u)\t%s\t%s\tnull\tSQRT;\n", oplen, des, des);
		break;
	case SIN:
		fprintf(out, "math(%u)\t%s\t%s\tnull\tSIN;\n", oplen, des, des);
		break;
	case COS:
		fprintf(out, "math(%u)\t%s\t%s\tnull\tCOS;\n", oplen, des, des);
		break;
	default:
		break;
	}
}

void command_cmp(unsigned short oplen, const char *type, const char *des, const char *src0,
				 const char *src1)
{
	fprintf(out, "cmp%s(%u)\t%s\t%s\t%s;\n", type, oplen, des, src0, src1);
}

void command_asr(unsigned short oplen, const char *des, const char *src0, const char *src1)
{
	fprintf(out, "asr(%u)\t%s\t%s\t%s;\n", oplen, des, src0, src1);
}

void command_shl(unsigned short oplen, const char *des, const char *src0, const char *src1)
{
	fprintf(out, "shl(%u)\t%s\t%s\t%s;\n", oplen, des, src0, src1);
}

void command_jmpi(unsigned short oplen, const char *index)
{
	fprintf(out, "jmpi(%u)\t%s;\n", oplen, index);
}

void output_lines(const char *str)
{
	fprintf(out, "%s\n", str);
}
